<div class="line tit">
	<h1>JavaScript 中的函数式编程实践</h1>
	<p class="label">邱俊涛, 软件工程师, Jinfonet</p>
</div>
<h2 class="line">基础知识</h2>
<h3 class="line">函数式编程简介</h2>
<p class="line">说到函数式编程，人们的第一印象往往是其学院派，晦涩难懂，大概只有那些蓬头散发，不修边幅，甚至有些神经质的大学教授们才会用的编程方式。这可能在历史上的某个阶段的确如此，但是近来函数式编程已经在实际应用中发挥着巨大作用了，而更有越来越多的语言不断的加入诸如 闭包，匿名函数等的支持，从某种程度上来讲，函数式编程正在逐步“同化”命令式编程。</p>
<p class="line">函数式编程思想的源头可以追溯到 20 世纪 30 年代，数学家阿隆左 . 丘奇在进行一项关于问题的可计算性的研究，也就是后来的 lambda 演算。lambda 演算的本质为 一切皆函数，函数可以作为另外一个函数的输出或者 / 和输入，一系列的函数使用最终会形成一个表达式链，这个表达式链可以最终求得一个值，而这个过程，即为计算的本质。</p>
<p class="line">然而，这种思想在当时的硬件基础上很难实现，历史最终选择了同丘奇的 lambda 理论平行的另一种数学理论：图灵机作为计算理论，而采取另一位科学家冯 . 诺依曼的计算机结构，并最终被实现为硬件。由于第一台计算机即为冯 . 诺依曼的程序存储结构，因此运行在此平台的程序也继承了这种基因，程序设计语言如 C/Pascal 等都在一定程度上依赖于此体系。</p>
<p class="line">到了 20 世纪 50 年代，一位 MIT 的教授 John McCarthy 在冯 . 诺依曼体系的机器上成功的实现了 lambda 理论，取名为 LISP(LISt Processor), 至此函数式编程语言便开始活跃于计算机科学领域。</p>
<h3 class="line">函数式编程语言特性</h3>
<p class="line">在函数式编程语言中，函数是第一类的对象，也就是说，函数 不依赖于任何其他的对象而可以独立存在，而在面向对象的语言中，函数 ( 方法 ) 是依附于对象的，属于对象的一部分。这一点 j 决定了函数在函数式语言中的一些特别的性质，比如作为传出 / 传入参数，作为一个普通的变量等。</p>
<p class="line">区别于命令式编程语言，函数式编程语言具有一些专用的概念，我们分别进行讨论：</p>
<h3 class="line">匿名函数</h3>
<p class="line">在函数式编程语言中，函数是可以没有名字的，匿名函数通常表示：“可以完成某件事的一块代码”。这种表达在很多场合是有用的，因为我们有时需要用函数完成某件事，但是这个函数可能只是临时性的，那就没有理由专门为其生成一个顶层的函数对象。比如：</p>

<p class="line">清单 1. map 函数</p>
<pre class="line">			
function map(array, func){ 
  	var res = []; 
  	for ( var i = 0, len = array.length; i < len; i++){ 
 		res.push(func(array[i])); 
	} 
  return res; 
} 
var mapped = map([1, 3, 5, 7, 8],  function (n){ 
  	return n = n + 1; 
}); 

print(mapped); 
</pre>
<p class="line">运行这段代码，将会打印：
<pre class="line">
2,4,6,8,9// 对数组 [1,3,5,7,8] 中每一个元素加 1 
</pre>
<p class="line">注意 map 函数的调用，map 的第二个参数为一个函数，这个函数对 map 的第一个参数 ( 数组 ) 中的每一个都有作用，但是对于 map 之外的代码可能没有任何意义，因此，我们无需为其专门定义一个函数，匿名函数已经足够。</p>
<h3 class="line">柯里化</h3>
<p class="line">柯里化是把接受多个参数的函数变换成接受一个单一参数（最初函数的第一个参数）的函数，并且返回接受余下的参数而且返回结果的新函数的技术。这句话有点绕口，我们可以通过例子来帮助理解：</p>

<p class="line">清单 2. 柯里化函数</p>
<pre class="line">				
function adder(num){ 
  	return function (x){ 
    	return num + x; 
  	} 
} 
var add5 = adder(5); 
var add6 = adder(6); 
print(add5(1)); 
print(add6(1)); 
</pre>
<p class="line">mapped 和 mapped2 均调用了 map，但是得到了截然不同的结果，因为 map 的参数本身已经进行了一次抽象，map 函数做的是第二次抽象，高阶的“阶”可以理解为抽象的层次。</p>

<h3 class="line">JavaScript 中的函数式编程</h3>
<p class="line">JavaScript 是一门被误解甚深的语言，由于早期的 Web 开发中，充满了大量的 copy-paste 代码，因此平时可以见到的 JavaScript 代码质量多半不高，而且 JavaScript 代码总是很飞动的不断闪烁的 gif 广告，限制网页内容的复制等联系在一起的，因此包括 Web 开发者在内的很多人根本不愿意去学习 JavaScript。</p>
<p class="line">这种情形在 Ajax 复兴时得到了彻底的扭转，Google Map，Gmail 等 Ajax 应用的出现使人们惊叹：原来 JavaScript 还可以做这样的事！很快，大量优秀的 JavaScript/Ajax 框架不断出现，比如 Dojo，Prototype，jQuery，ExtJS 等等。这些代码在给页面带来绚丽的效果的同时，也让开发者看到函数式语言代码的优雅。</p>
<h3 class="line">函数式编程风格</h3>
<p class="line">在 JavaScript 中，函数本身为一种特殊对象，属于顶层对象，不依赖于任何其他的对象而存在，因此可以将函数作为传出 / 传入参数，可以存储在变量中，以及一切其他对象可以做的事情 ( 因为函数就是对象 )。</p>
<p class="line">JavaScript 被称为有着 C 语法的 LISP，LISP 代码的一个显著的特点是大量的括号以及前置的函数名，比如：</p>

<p class="line">清单 5. JavaScript 中的求和</p>
<pre class="line">				
function sum(){ 
  	var res = 0; 
  	for ( var i = 0, len = arguments.length; i < len; i++){ 
 		res += parseInt(arguments[i]); 
	} 
  	return res; 
} 

print(sum(1,2,3)); 
print(sum(1,2,3,4,6,7,8)); 
</pre>

<p class="line">如果要完全模拟函数式编码的风格，我们可以定义一些诸如：</p>

<p class="line">清单 6. 一些简单的函数抽象</p>
<pre class="line">
function add(a, b){  return a+b; } 
function sub(a, b){  return a-b; } 
function mul(a, b){  return a*b; } 
function div(a, b){  return a/b; } 
function rem(a, b){  return a%b; } 
function inc(x){  return x + 1; } 
function dec(x){  return x - 1; } 
function equal(a, b){  return a==b; } 
function great(a, b){  return a>b; } 
function less(a, b){  return a&gt;b; } 
</pre>
<p class="line">这样的小函数以及谓词，那样我们写出的代码就更容易被有函数式编程经验的人所接受：</p>

<p class="line">清单 7. 函数式编程风格</p>
<pre class="line">				
// 修改之前的代码
function factorial(n){ 
	if (n == 1){ 
		return 1; 
	} else { 
		return factorial(n - 1) * n; 
	} 
} 

// 更接近“函数式”编程风格的代码
function factorial(n){ 
 	if (equal(n, 1)){ 
     	return 1; 
	} else { 
     	return mul(n, factorial(dec(n))); 
	} 
} 
</pre>
<h3 class="line">实际应用中的例子</h3>
<p class="line">好了，理论知识已经够多了，我们下面来看看现实世界中的 JavaScript 函数式编程。有很多人为使 JavaScript 具有面向对象风格而做出了很多努力 (JavaScript 本身具有 可编程性)，事实上，面向对象并非必须，使用函数式编程或者两者混合使用可以使代码更加优美，简洁。</p>
<p class="line">jQuery 是一个非常优秀 JavaScript/Ajax 框架，小巧，灵活，具有插件机制，事实上，jQuery 的插件非常丰富，从表达验证，客户端图像处理，UI，动画等等。而 jQuery 最大的特点正如其宣称的那样，改变了人们编写 JavaScript 代码的风格。</p>
<p class="line">优雅的 jQueryv
<p class="line">有经验的前端开发工程师会发现，平时做的最多的工作有一定的模式：选择一些 DOM 元素，然后将一些规则作用在这些元素上，比如修改样式表，注册事件处理器等。因此 jQuery 实现了完美的 CSS 选择器，并提供跨浏览器的支持：</p>

<p class="line">清单 15. jQuery 选择器</p>
<pre class="line">				
var cons = $("div.note");// 找出所有具有 note 类的 div 
var con = $("div#con");// 找出 id 为 con 的 div 元素
var links = $("a");// 找出页面上所有的链接元素
</pre>
<p class="line">当然，jQuery 的选择器规则非常丰富，这里要说的是：用 jQuery 选择器选择出来的 jQuery 对象本质上是一个 List，正如 LISP 语言那样，所有的函数都是基于 List 的。</p>
<p class="line">有了这个 List，我们可以做这样的动作：</p>

<p class="line">清单 16. jQuery 操作 jQuery 对象 (List)</p>
<pre class="line">					
cons.each( function (index){ 
 	$( this ).click( function (){ 
 		//do something with the node 
	}); 
}); 
</pre>
<p class="line">想当与对 cons 这个 List中的所有元素使用 map( 还记得我们前面提到的 map 吗？ )，操作结果仍然为一个 List。我们可以任意的扩大 / 缩小这个列表，比如：</p>

<p class="line">清单 17. 扩大 / 缩小 jQuery 集合</p>
<pre class="line">			
cons.find("span.title");// 在 div.note 中进行更细的筛选
cons.add("div.warn");// 将 div.note 和 div.warn 合并起来
cons.slice(0, 5);// 获取 cons 的一个子集
</pre>
<p class="line">我们通过 jQuery 对包装集进行一次过滤，jQuery 的过滤函数可以使得选择出来的列表对象只保留符合条件的，在这个例子中，我们保留这样的 div，当且仅当这个 div 中包含一个类名为 title 的 span，并且这个 span 的内容为数字：</p>

<p class="line">清单 19. 过滤集合</p>
<pre class="line">			
var cons = $("div.note").hide();// 选择 note 类的 div, 并隐藏
cons.filter( function (){ 
	return $( this ).find("span.title").html().match(/^\d+$/); 
}).show(); 
</pre>
<p class="line">效果如下图所示：</p>
<div class="line img">
<img src="http://www.ibm.com/developerworks/cn/web/1006_qiujt_jsfunctional/image003.gif" style="height:8em">
<p class="imgtext">图 2. 过滤之后的效果</p>
</div>
<p class="line">我们再来看看 jQuery 中对数组的操作 ( 本质上来讲，JavaScript 中的数组跟 List 是很类似的 )，比如我们在前面的例子中提到的 map 函数，过滤器等：</p>

<p class="line">清单 20. jQuery 对数组的函数式操作
<pre class="line">				
var mapped = $.map([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 
	function (n){ 
		return n + 1; 
	}); 
var greped = $.grep([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 
	function (n){ 
		return n % 2 == 0; 
	}); 
</pre>
<p class="line">mapped 将被赋值为 :</p>
<pre class="line">[2, 3, 4, 5, 6, 7, 8, 9, 10, 11] </pre>

<p class="line">而 greped 则为：</p>
<pre class="line">[2, 4, 6, 8, 10] </pre>

<p class="line">我们再来看一个更接近实际的例子：</p>

<p class="line">清单 21. 一个页面刷新的例子</p>
<pre class="line">				
function update(item){ 
  	return function (text){ 
 		$("div#"+item).html(text); 
	} 
} 
function refresh(url, callback){ 
  	var params = { 
 		type : "echo", 
 		data : ""
	}; 
 	$.ajax({ 
 		type:"post", 
		url:url, 
 		cache: false , 
 		async: true , 
 		dataType:"json", 
		data:params, 
		
 		success:  function (data, status){ 
			callback(data); 
		}, 
		
 		error:  function (err){ 
 			alert("error : "+err); 
		} 
	 }); 
} 
refresh("action.do/op=1", update("content1")); 
refresh("action.do/op=2", update("content2")); 
refresh("action.do/op=3", update("content3")); 
</pre>
<p class="line">首先声明一个柯里化的函数 update，这个函数会将传入的参数作为选择器的 id，并更新这个 div 的内容 (innerHTML)。然后声明一个函数 refresh，refresh 接受两个参数，第一个参数为服务器端的 url，第二个参数为一个回调函数，当服务器端成功返回时，调用该函数。</p>
<p class="line">然后我们陆续调用三次 refresh，每次的 url 和 id 都不同，这样可以将 content1,content2,conetent3 的内容通过异步方式更新。这种模式在实际的编程中相当有效，因为关于如何与服务器通信，以及如果选取页面内容的部分被很好的抽象成函数，现在我们需要做的就是将 url 和 id 传递给 refresh，即可完成需要的动作。函数式编程在很大程度上降低了这个过程的复杂性，这正是我们选择使用该思想的最终原因。</p>
<h2 class="line">结束语</h2>
<p class="line">
实际的应用中，不会囿于函数式或者面向对象，通常是两者混合使用，事实上，很多主流的面向对象语言都在不断的完善自己，比如加入一些函数式编程语言的特征等，JavaScript 中，这两者得到了良好的结合，代码不但可以非常简单，优美，而且更易于调试。</p>
<p class="line">文中仅仅提到 jQuery 特征的一小部分，如果感兴趣，则可以在参考资料中找到更多的链接，jQuery 非常的流行，因此你可以找到很多论述如何使用它的文章。</p>
